/**
 * @author hfutrell
 */
//
//  IndexedModel.js
//  Renderbunny
//
//  Created by Holmes Futrell on 1/21/11.
//  Copyright 2011 Holmes Futrell. All rights reserved.
//


function flatten(input) {
	var result = [];
	var i, j;
	for (i = 0; i < input.length; i++) {
		var inner = input[i];
		for (j = 0; j < inner.length; j++) {
			result.push(inner[j]);
		}
	}
	return result;
}

function indexedModel(s) {
	
	var startTime = (new Date).getTime();
	
	this.attribMapping = [];
	var i;
	for (i=0; i<indexedModel.bufferIndices.NUM_BUFFERS; i++) {
		this.attribMapping[i] = -1;
	}
	
	this.htable = [];
		
	this.indices = [];
	this.buffers = [];
	
	this.num_indexed_vertices = 0;
	this.num_indices = 0;
	
	var num_normals = 0;
	var num_vertices = 0;
	var num_tex_coords = 0;
	
	var temp_vertices							= [];
	var temp_normals							= [];
	var temp_texcoords							= [];
	var indexed_vertices						= [];
	var indexed_normals							= [];
	var indexed_texcoords						= [];
	var indexed_tangents						= [];
			
	var lines = s.split("\n");
	
    var floatMatch = "([-+]?\\d*\\.?\\d+)"; // pattern to match floating point number
    var intMatch = "([+]?[1-9]\\d*)"; // pattern to match int
    var frac = intMatch + "/" + intMatch + "/" + intMatch; // pattern to match vertex list
    var vertexmatch = "^v\\s+" + floatMatch + "\\s+" + floatMatch + "\\s+" + floatMatch;
    var texcoordmatch = "^vt\\s+" + floatMatch + "\\s+" + floatMatch;
    var normalmatch = "^vn\\s+" + floatMatch + "\\s+" + floatMatch + "\\s+" + floatMatch;
    var trianglematch = "^f\\s*" + frac + "\\s*" + frac + "\\s*" + frac;
	
    var innerGetIndex = function(v, t, n, obj){
        var ii = obj.lookupVertex(v, t, n);
        if (ii === undefined) {
            ii = obj.addVertexWithIndividualIndices(v, t, n);
            indexed_vertices[ii] = temp_vertices[v - 1];
            indexed_normals[ii] = temp_normals[n - 1];
            indexed_texcoords[ii] = temp_texcoords[t - 1];
            indexed_tangents[ii] = [0.0, 0.0, 0.0];
        }
        return ii;
    };

	var x;
	for (x=0; x < lines.length; x++){
				
		var line = lines[x];
		var firstTwo = line.substr(0,2);
		
		var result;
		if (firstTwo === "v " && (result = line.match(vertexmatch)) !== null) {
			temp_vertices[num_vertices++] = [parseFloat(result[1]),parseFloat(result[2]),parseFloat(result[3])];
		}
		else if (firstTwo === "vt" && (result = line.match(texcoordmatch)) !== null) {
			temp_texcoords[num_tex_coords++] = [parseFloat(result[1]), parseFloat(result[2])];
		}
		else if (firstTwo === "vn" && (result = line.match(normalmatch)) !== null) {
			temp_normals[num_normals++] = [parseFloat(result[1]),parseFloat(result[2]),parseFloat(result[3])];
		} // quads
		else if (firstTwo === "f " && (result = line.match(trianglematch)) !== null) {
			
			var triangle = {
				v1: parseInt(result[1], 10),
				t1: parseInt(result[2], 10),
				n1: parseInt(result[3], 10),
				v2: parseInt(result[4], 10),
				t2: parseInt(result[5], 10),
				n2: parseInt(result[6], 10),
				v3: parseInt(result[7], 10),
				t3: parseInt(result[8], 10),
				n3: parseInt(result[9], 10)
			};
			
			var i1 = innerGetIndex(triangle.v1, triangle.t1, triangle.n1, this);
			var i2 = innerGetIndex(triangle.v2, triangle.t2, triangle.n2, this);
			var i3 = innerGetIndex(triangle.v3, triangle.t3, triangle.n3, this);			
			this.addIndexedTriangle(i1, i2, i3);
			// compute tangent for this triangle
			var Q1 = $V(indexed_vertices[i2]).subtract($V(indexed_vertices[i1]));
			var Q2 = $V(indexed_vertices[i3]).subtract($V(indexed_vertices[i1]));
			var t1 = indexed_texcoords[i2][1] - indexed_texcoords[i1][1];
			var t2 = indexed_texcoords[i3][1] - indexed_texcoords[i1][1];
			// t2 * Q1 - t1 * Q2
			var tangent = Q1.multiply(t2).subtract(Q2.multiply(t1));
			// add triangle tangent to vertex tangents
			indexed_tangents[i1] = tangent.add($V(indexed_tangents[i1])).elements;
			indexed_tangents[i2] = tangent.add($V(indexed_tangents[i2])).elements;
			indexed_tangents[i3] = tangent.add($V(indexed_tangents[i3])).elements;
		}
		else if ( (result = line.match("^(usemtl|mtllib|g)\\s+.*")) !== null) {
			continue;
		}
		else if (line.charAt(0) === '#') {
			// comment, null terminator, or empty string
			continue;
		}
		else if ((result = line.match("\\s*")) !== null) {
			continue; // line of whitespace (or empty)
		}
		else {
			console.warn("Cannot parse line " + line);
		}
		
	}
	
	/*
	  normalize tangent vectors (averages adjacent tangents)
	 */
	for (i=0; i<this.num_indexed_vertices; i++) {
		indexed_tangents[i] = $V(indexed_tangents[i]).toUnitVector().elements;
	}
				
	//console.log("Vertices = " + num_vertices + "\n");
	//console.log("TexCoords = " + num_tex_coords + "\n");
	//console.log("Normals = " + num_normals + "\n");
	//console.log("Indexed Vertices = " + this.num_indexed_vertices + "\n");
	//console.log("Num Triangles = " + this.num_indices / 3 + "\n");
	
	for (i=0; i<indexedModel.bufferIndices.NUM_BUFFERS; i++) {
		this.buffers[i] = gl.createBuffer();
	}
		
	var diff = (new Date).getTime() - startTime;
		
	console.log("took " + diff + "ms to load model");
		
	// generate the buffers
	gl.bindBuffer(gl.ARRAY_BUFFER, this.buffers[indexedModel.bufferIndices.NORMAL]);
	gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(flatten(indexed_normals)), gl.STATIC_DRAW);
	gl.bindBuffer(gl.ARRAY_BUFFER, this.buffers[indexedModel.bufferIndices.UV]);
	gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(flatten(indexed_texcoords)), gl.STATIC_DRAW);
	gl.bindBuffer(gl.ARRAY_BUFFER, this.buffers[indexedModel.bufferIndices.VERTEX]);
	gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(flatten(indexed_vertices)), gl.STATIC_DRAW);
	gl.bindBuffer(gl.ARRAY_BUFFER, this.buffers[indexedModel.bufferIndices.TANGENTS]);
	gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(flatten(indexed_tangents)), gl.STATIC_DRAW);

	gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.buffers[indexedModel.bufferIndices.ELEMENTS]);
	gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(this.indices), gl.STATIC_DRAW);

	gl.bindBuffer(gl.ARRAY_BUFFER, null);
	gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);

	delete this.htable;

			
}

indexedModel.prototype.htable = null;
indexedModel.prototype.num_indices = 0;
indexedModel.prototype.num_indexed_vertices = 0;
indexedModel.prototype.buffers = null;
indexedModel.prototype.attribMapping = null;
indexedModel.prototype.indices = null;

indexedModel.bufferIndices = {
    VERTEX: 0,
    NORMAL: 1,
    TANGENTS: 2,
    UV: 3,
    ELEMENTS: 4,
    NUM_BUFFERS: 5
};

indexedModel.prototype.mapBufferToAttributeLocation = function(buffer, location) {
	if ( buffer < 0 || buffer >= indexedModel.bufferIndices.NUM_BUFFERS) {
		console.log("Invalid buffer!");
	}
	else {
		this.attribMapping[buffer] = location;
	}
};


indexedModel.prototype.draw = function() {
	// set up vertices
	if (this.attribMapping[indexedModel.bufferIndices.VERTEX] !== -1) {
		gl.bindBuffer(gl.ARRAY_BUFFER, this.buffers[indexedModel.bufferIndices.VERTEX]);	
		gl.enableVertexAttribArray(this.attribMapping[indexedModel.bufferIndices.VERTEX]);	
		gl.vertexAttribPointer(this.attribMapping[indexedModel.bufferIndices.VERTEX], 3, gl.FLOAT, 0, 0, 0);
	}
		
	// set up normals
	if (this.attribMapping[indexedModel.bufferIndices.NORMAL] !== -1) {
		gl.bindBuffer(gl.ARRAY_BUFFER, this.buffers[indexedModel.bufferIndices.NORMAL]);	
		gl.enableVertexAttribArray(this.attribMapping[indexedModel.bufferIndices.NORMAL]);	
		gl.vertexAttribPointer(this.attribMapping[indexedModel.bufferIndices.NORMAL], 3, gl.FLOAT, 0, 0, 0);
	}
	
	// set up tex coords
	if (this.attribMapping[indexedModel.bufferIndices.UV] !== -1) {
		gl.bindBuffer(gl.ARRAY_BUFFER, this.buffers[indexedModel.bufferIndices.UV]);		
		gl.enableVertexAttribArray(this.attribMapping[indexedModel.bufferIndices.UV]);
		gl.vertexAttribPointer(this.attribMapping[indexedModel.bufferIndices.UV], 2, gl.FLOAT, 0, 0, 0);	
	}
	
	// set up tex tangents
	if (this.attribMapping[indexedModel.bufferIndices.TANGENTS] !== -1) {
		gl.bindBuffer(gl.ARRAY_BUFFER, this.buffers[indexedModel.bufferIndices.TANGENTS]);		
		gl.enableVertexAttribArray(this.attribMapping[indexedModel.bufferIndices.TANGENTS]);
		gl.vertexAttribPointer(this.attribMapping[indexedModel.bufferIndices.TANGENTS], 3, gl.FLOAT, 0, 0, 0);	
	}
		
	// Update attribute values.
	gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.buffers[indexedModel.bufferIndices.ELEMENTS]);
	// Draw
	gl.drawElements(gl.TRIANGLES, this.num_indices, gl.UNSIGNED_SHORT, 0);
	// Unbind the buffers	
	gl.bindBuffer(gl.ARRAY_BUFFER, null);
	gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
	
	if (this.attribMapping[indexedModel.bufferIndices.TANGENTS] !== -1) {
		gl.disableVertexAttribArray(this.attribMapping[indexedModel.bufferIndices.TANGENTS]);
	}
	if (this.attribMapping[indexedModel.bufferIndices.UV] !== -1) {
		gl.disableVertexAttribArray(this.attribMapping[indexedModel.bufferIndices.UV]);
	}
	if (this.attribMapping[indexedModel.bufferIndices.NORMAL] !== -1) {
		gl.disableVertexAttribArray(this.attribMapping[indexedModel.bufferIndices.NORMAL]);
	}
	if (this.attribMapping[indexedModel.bufferIndices.VERTEX] !== -1) {
		gl.disableVertexAttribArray(this.attribMapping[indexedModel.bufferIndices.VERTEX]);
	}
};

indexedModel.prototype.addIndexedTriangle = function(_i1, _i2, _i3) {
	this.indices[this.num_indices++] = _i1;
	this.indices[this.num_indices++] = _i2;
	this.indices[this.num_indices++] = _i3;	
};

/*
	Given vertex information along with a set of indices, adds a new vertex to the list of vertices
	inserts this vertex into the hash table so that the new vertex index can be located by individual indexes
 */
 
indexedModel.prototype.addVertexWithIndividualIndices = function(i1, i2, i3) {
	var index = this.num_indexed_vertices;
	this.num_indexed_vertices++;
	var key = "("+i1+","+i2+","+i3+")";
	this.htable[key] = index;
	return index;
};

indexedModel.prototype.lookupVertex = function(i1, i2, i3) {
	// lookup in hash table here
	var key = "("+i1+","+i2+","+i3+")";
	return this.htable[key];	
};